
#include "EggNinePCH.h"

#include "gl/glew.h"
#include "ublocks.h"

#include <cassert>

namespace EggNine {


UniformBlock::UniformBlock(const char* name, int size, void* data, GLenum purpose)
    : m_name(name)
    , m_size(size)
    , m_dirty(false)
    , m_data(data)
    , m_purpose(purpose)
{
    glGenBuffers(1, &m_id);
    glBindBuffer(GL_UNIFORM_BUFFER, m_id);
    glBufferData(GL_UNIFORM_BUFFER, m_size, NULL, m_purpose);
    glBindBuffer(GL_UNIFORM_BUFFER, 0);
}

UniformBlock::~UniformBlock()
{
    glDeleteBuffers(1, &m_id);
}

void UniformBlock::SetData(void* data)
{
    memcpy(m_data, data, m_size);
    m_dirty=true;
}

void UniformBlock::SetData(void* data) const
{
    throw "Unable to set const data in this uniform";
}

void UniformBlock::BindToProgram( GLuint program, GLuint bindingSlot ) const
{
    GLint block=glGetUniformBlockIndex(program, m_name.c_str());
    assert(block!=-1);
    GLint blockSize;
    glGetActiveUniformBlockiv(program, block, GL_UNIFORM_BLOCK_DATA_SIZE, &blockSize);
    assert(m_size==blockSize);
    glUniformBlockBinding(program, block, bindingSlot);
}

void UniformBlock::BindToState(GLuint bindingSlot) const
{
    glBindBufferBase(GL_UNIFORM_BUFFER, bindingSlot, m_id);
}

void UniformBlock::Update() const
{
    if(m_dirty) {
        glBindBuffer(GL_UNIFORM_BUFFER, m_id);

#if 1
        glBufferData(GL_UNIFORM_BUFFER, m_size, NULL, m_purpose);
        glBufferSubData(GL_UNIFORM_BUFFER, 0, m_size, m_data);
#else
        // This should have better performance than above
        // However, it seems like quite the same, and feels more dangerous
        void *data=glMapBufferRange(GL_UNIFORM_BUFFER, 0, m_size, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT | GL_MAP_UNSYNCHRONIZED_BIT);
        memcpy(data, m_data, m_size);
        glUnmapBuffer(GL_UNIFORM_BUFFER);
#endif
        glBindBuffer(GL_UNIFORM_BUFFER, 0);
        m_dirty=false;
    }
}

} // EggNine
